package org.budo.dubbo.protocol.http.dispatcher.servlet;

import java.io.IOException;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.budo.dubbo.protocol.http.BudoDubboHttpProtocol;
import org.budo.dubbo.protocol.http.api.RegistryApi;
import org.budo.dubbo.protocol.http.api.impl.RegistryApiImpl;
import org.budo.dubbo.protocol.http.exception.remoting.RemotingRuntimeException;
import org.budo.dubbo.protocol.http.exporter.util.ExportUtil;
import org.budo.dubbo.protocol.http.invocation.BudoDubboRpcInvocation;
import org.budo.dubbo.protocol.http.invocation.adapter.InvocationAdapter;
import org.budo.dubbo.protocol.http.page.PageDispatcher;
import org.budo.dubbo.protocol.http.spring.web.BudoHandlerMethod;
import org.budo.dubbo.protocol.http.view.render.impl.RawViewRender;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.mvcs.MvcsHandlerInterceptor;
import org.springframework.beans.factory.InitializingBean;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcResult;

import lombok.extern.slf4j.Slf4j;

/**
 * @see com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet
 * @see org.budo.dubbo.protocol.http.BudoDubboHttpInvoker
 * @see org.budo.dubbo.protocol.http.BudoDubboHttpProtocol
 * 
 * @author lmw
 */
@Slf4j
public class BudoDubboHttpProtocolDispatcherServlet extends AbstractBudoDubboHttpProtocolDispatcherServlet implements InitializingBean {
    private static final long serialVersionUID = -29981585304849192L;

    private static final String DEFAULT_VIEW_RENDER = "defaultViewRender";

    private static final String DEFAULT_INVOCATION_ADAPTER = "defaultInvocationAdapter";

    private PageDispatcher pageDispatcher = new PageDispatcher();

    /**
     * 初始化
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        String defaultInvocationAdapter = config.getInitParameter(DEFAULT_INVOCATION_ADAPTER);
        if (!StringUtils.isEmpty(defaultInvocationAdapter)) {
            ReflectUtil.classForName(defaultInvocationAdapter); // check class exists
            this.setDefaultInvocationAdapter(defaultInvocationAdapter);
        }

        String defaultViewRender = config.getInitParameter(DEFAULT_VIEW_RENDER);
        if (!StringUtils.isEmpty(defaultViewRender)) {
            ReflectUtil.classForName(defaultViewRender); // check class exists
            this.setDefaultViewRender(defaultViewRender);
        }

        this.afterInit();

        log.info("#70 init done, this=" + this //
                + ", config=" + config //
                + ", defaultInvocationAdapter=" + defaultInvocationAdapter //
                + ", defaultViewRender=" + defaultViewRender);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.afterInit();
    }

    private void afterInit() {
        ExportUtil.newServiceConfig(RegistryApi.class, new RegistryApiImpl()).export(); // exportDefaultServices
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        DispatcherServletUtil.beforeService(servletRequest, servletResponse);

        super.service(servletRequest, servletResponse);

        DispatcherServletUtil.afterService(servletRequest, servletResponse);
    }

    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        super.doOptions(request, response);
    }

    /**
     * GET 可能是接口请求，也可能是ApiDoc
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        BudoDubboRpcInvocation invocation = this.adaptBudoInvocation(request, response);
        if (null == invocation) {
            pageDispatcher.service(request, response);
            return;
        }

        this.handleBudoInvocation(request, response, invocation);
    }

    /**
     * POST 一定是接口请求
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        BudoDubboRpcInvocation invocation = this.adaptBudoInvocation(request, response);
        if (null == invocation) {
            return;
        }

        this.handleBudoInvocation(request, response, invocation);
    }

    /**
     * 执行请求对应的接口方法并返回
     */
    private void handleBudoInvocation(HttpServletRequest request, HttpServletResponse response, BudoDubboRpcInvocation invocation) {
        BudoDubboHttpProtocol protocol = BudoDubboHttpProtocol.getInstance();
        Exporter<?> exporter = this.findInterfaceExporter(invocation, protocol); // 解析请求并找到对应的接口

        if (null == exporter) {
            this.handleExporterNotFound(request, response, invocation, protocol);
            return;
        }

        try {
            if (DispatcherServletUtil.HAS_BUDO_SUPPORT_MVCS) { // 拓展，使可以通过Mvcs取得当前请求的接口
                BudoHandlerMethod handlerMethod = new BudoHandlerMethod(invocation.getInterfaceName(), invocation.getMethodName(), invocation.getParameterTypes());
                request.setAttribute(MvcsHandlerInterceptor.HANDLER_IN_REQUEST_ATTRIBUTE_KEY, handlerMethod);
            }

            Invoker<?> invoker = exporter.getInvoker();
            Result result = invoker.invoke(invocation); // 执行方法

            this.renderInvokeResult(request, response, invocation, invoker, result); // 渲染返回结果
            return;
        } catch (Throwable error) {
            RpcResult result = new RpcResult(error);
            this.render(request, response, result); // 执行异常，但并不是所有异常都会进入这里，业务代码里的异常，不会到这里来，比如 validation 异常
            return;
        }
    }

    /**
     * 解析请求内容，返回BudoDubboRpcInvocation
     */
    private BudoDubboRpcInvocation adaptBudoInvocation(HttpServletRequest request, HttpServletResponse response) {
        InvocationAdapter invocationAdapter = this.getInvocationAdapter(request);

        try {
            return invocationAdapter.adaptInvocation(new ServletProtocolRequest(request));
        } catch (Throwable e) {
            if (e instanceof RemotingRuntimeException) {
                this.render(request, response, e);
                return null;
            }

            String message = "#163 invocationAdapter.adaptInvocation error"//
                    + ", request=" + request //
                    + ", requestURI=" + request.getRequestURI() //
                    + ", remoteAddr=" + request.getRemoteAddr() //
                    + ", userAgent=" + request.getHeader("User-Agent") //
                    + ", invocationAdapter=" + invocationAdapter;

            RemotingRuntimeException error = new RemotingRuntimeException(message, e);
            this.render(request, response, error);
            return null;
        }
    }

    /**
     * 查找当前请求对应的接口
     */
    private Exporter<?> findInterfaceExporter(BudoDubboRpcInvocation invocation, BudoDubboHttpProtocol protocol) {
        String interfaceName = invocation.getInterfaceName();

        if (null == protocol) {
            return null;
        }

        Map<String, Exporter<?>> exporterMap = protocol.getExporterMap();
        if (null == exporterMap) {
            return null;
        }

        return exporterMap.get(interfaceName);
    }

    /**
     * 未找到请求对应的接口时的处理
     */
    private void handleExporterNotFound(HttpServletRequest request, HttpServletResponse response, BudoDubboRpcInvocation budoInvocation, BudoDubboHttpProtocol budoDubboHttpProtocol) {
        String interfaceName = budoInvocation.getInterfaceName();

        if (log.isDebugEnabled()) {
            log.debug("#199 doPost error, exporter is interfaceName=" + interfaceName + ", this=" + budoDubboHttpProtocol);
        }

        String message = "#202 not found exported service: " + interfaceName //
                + ", budoDubboHttpProtocol=" + budoDubboHttpProtocol;
        RemotingRuntimeException error = new RemotingRuntimeException(message);
        this.render(request, response, error);
        return;
    }

    /**
     * 渲染接口方法执行返回的值，或者异常
     */
    private void renderInvokeResult(HttpServletRequest request, HttpServletResponse response, BudoDubboRpcInvocation budoInvocation, Invoker<?> invoker, Result result) throws Throwable {
        if (budoInvocation.getResponseBody()) { // 如果有@ResponseBody注解
            return; // 不走常规结果渲染流程
        }

        // 框架内部通信，使用 RawViewRender, @see exportDefaultServices
        if (invoker.getInterface().equals(RegistryApi.class)) {
            RawViewRender.INSTANCE.renderView(new ServletProtocolRequest(request), new ServletProtocolResponse(response), result);
            return;
        }

        // 业务请求
        this.render(request, response, result); // 执行正常
        return;
    }
}
